package com.atguigu.gmall.order.service;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.cart.api.GmallCartApi;
import com.atguigu.gmall.cart.pojo.Cart;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.common.exception.CartException;
import com.atguigu.gmall.common.exception.OrderException;
import com.atguigu.gmall.oms.api.GmallOmsApi;
import com.atguigu.gmall.oms.vo.OrderItemVo;
import com.atguigu.gmall.oms.vo.OrderSubmitVo;
import com.atguigu.gmall.order.feign.*;
import com.atguigu.gmall.order.interceptor.LoginInterceptor;
import com.atguigu.gmall.order.vo.OrderConfirmVo;
import com.atguigu.gmall.order.vo.UserInfo;
import com.atguigu.gmall.pms.entity.SkuAttrValueEntity;
import com.atguigu.gmall.pms.entity.SkuEntity;
import com.atguigu.gmall.sms.vo.ItemSaleVo;
import com.atguigu.gmall.ums.entity.UserAddressEntity;
import com.atguigu.gmall.ums.entity.UserEntity;
import com.atguigu.gmall.wms.entity.WareSkuEntity;
import com.atguigu.gmall.wms.vo.SkuLockVo;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.thymeleaf.TemplateEngine;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

/**
 * 由于存在大量的远程调用 使用异步编排节省时间 引入线程池
 *
 * @author Jia Fa
 * @create 2021-12-21 1:15
 */
@Service
public class OrderService {

    @Autowired
    GmallPmsClient pmsClient;
    @Autowired
    GmallSmsItemClient smsClient;
    @Autowired
    GmallWmsClient wmsClient;
    @Autowired
    GmallUmsClient umsClient;
    @Autowired
    GmallCartClient cartClient;
    @Autowired
    private GmallOmsClient omsClient;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ExecutorService executorService;  // 线程池对象
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    TemplateEngine templateEngine;  // 模板引擎

    private static final String KEY_PREFIX = "order:token:";


    public OrderConfirmVo confirm() {
        // 订单确认页所需的数据接口：
        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();

        UserInfo userInfo = LoginInterceptor.getUserInfo();
        Long userId = userInfo.getUserId();
        String username = userInfo.getUsername();

//      1.根据userId获取用户的地址列表
        CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
            //runAsync  无参数有无返回值
            ResponseVo<List<UserAddressEntity>> addressResponseVo = this.umsClient.queryUserAddressByUserId(userId);
            List<UserAddressEntity> addressEntities = addressResponseVo.getData();

            orderConfirmVo.setAddresses(addressEntities);
        }, executorService);

//		2.根据userId查询用户选中的购物车信息 skuId count
        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {

            ResponseVo<List<Cart>> cartResponseVo = this.cartClient.queryCheckCartByUserId(userId);
            List<Cart> carts = cartResponseVo.getData();
            if (CollectionUtils.isEmpty(carts)) {
                throw new CartException("没有选中的订单");
            }
            // 将购物车集合转为送货清单集合
            List<OrderItemVo> orderItems = carts.stream().map(cart -> {
                OrderItemVo orderItemVo = new OrderItemVo();
                // 购物车中仅有skuId和count数据 其他数据需要远程调用
                Long skuId = cart.getSkuId();
                orderItemVo.setCount(cart.getCount());
                orderItemVo.setSkuId(skuId);


                //3.根据skuId查询sku信息
                ResponseVo<SkuEntity> skuEntityResponseVo = this.pmsClient.querySkuById(skuId);
                SkuEntity skuEntity = skuEntityResponseVo.getData();
                if (skuEntity != null) {
                    orderItemVo.setDefaultImage(skuEntity.getDefaultImage());
                    orderItemVo.setPrice(skuEntity.getPrice());
                    orderItemVo.setTitle(skuEntity.getTitle());
                    orderItemVo.setWeight(skuEntity.getWeight());
                }

                // 4.根据skuId查询销售属性
                ResponseVo<List<SkuAttrValueEntity>> saleAttrResponseVo = this.pmsClient.querySaleAttrValuesBySkuId(skuId);
                List<SkuAttrValueEntity> attrValueEntities = saleAttrResponseVo.getData();
                if (!CollectionUtils.isEmpty(attrValueEntities)) {
                    orderItemVo.setSaleAttrs(attrValueEntities);
                }

                // 6.根据skuId查询营销优惠信息
                ResponseVo<List<ItemSaleVo>> saleSmsResponseVo = this.smsClient.queryItemSaleBySkuId(skuId);
                List<ItemSaleVo> saleSmsResponseVoData = saleSmsResponseVo.getData();
                orderItemVo.setSales(saleSmsResponseVoData);

                // 5.根据skuId查询库存信息
                ResponseVo<List<WareSkuEntity>> wareResponseVo = this.wmsClient.queryWareSkuBySid(skuId);
                List<WareSkuEntity> wareResponseVoData = wareResponseVo.getData();
                if (!CollectionUtils.isEmpty(wareResponseVoData)) {
                    orderItemVo.setStore(wareResponseVoData.stream().anyMatch(wareSkuEntity -> wareSkuEntity.getStock() - wareSkuEntity.getStockLocked() > 0));
                }
                return orderItemVo;
            }).collect(Collectors.toList());
            orderConfirmVo.setOrderItems(orderItems);
        }, executorService);


//		7.根据UserId查询用户信息（获取积分）
        CompletableFuture<Void> integrationFuture = CompletableFuture.runAsync(() -> {

            ResponseVo<UserEntity> userEntityResponseVo = this.umsClient.queryUserById(userId);
            UserEntity userEntity = userEntityResponseVo.getData();
            if (userEntity != null) {

                orderConfirmVo.setBounds(userEntity.getIntegration());
            }
        }, executorService);

//		防重 生成一个唯一的orderToken 页面和Redis都需要一份  使用雪花算法
        CompletableFuture<Void> tokenFuture = CompletableFuture.runAsync(() -> {

            String orderToken = IdWorker.getIdStr();
            orderConfirmVo.setOrderToken(orderToken);
            this.redisTemplate.opsForValue().set(KEY_PREFIX + orderToken, orderToken);
        }, executorService);

        CompletableFuture.allOf(addressFuture,cartFuture,integrationFuture,tokenFuture).join();
        return orderConfirmVo;
    }

    /**
     * 提交订单方法
     *
     * @param submitVo
     */
    public void submit(OrderSubmitVo submitVo) {
        // 1 防重 Redis中是否存在token信息
        String orderToken = submitVo.getOrderToken();
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                "then return redis.call('del', KEYS[1]) " +
                "else return 0 end";
        // 当第一次提交时 立刻删除Redis中的token数据 防止重复提交   Arrays.asList(KEY_PREFIX + orderToken) key集合 存储的是key值  后面是不定参数argv
        Boolean flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(KEY_PREFIX + orderToken), orderToken);
        if (!flag){
            //说明不是第一次删除了
            throw new OrderException("不可重复提交");
        }

        // 2 验总价 页面上的总价格和数据库的实时总价格
        BigDecimal totalPrice = submitVo.getTotalPrice(); // 页面总价
        List<OrderItemVo> items = submitVo.getItems(); // 订单详情 查询实时总价
        if (CollectionUtils.isEmpty(items)){
            throw new OrderException("订单中没有选中的商品");
        }
        // 遍历订单详情,获取数据库最新实时总价
        BigDecimal currentPrice = items.stream().map(item -> {   // item -> OrderItemVo
            // 获取每个sku商品的实时价格集合  每个item中仅有一个sku
            ResponseVo<SkuEntity> skuEntityResponseVo = this.pmsClient.querySkuById(item.getSkuId());
            SkuEntity skuEntity = skuEntityResponseVo.getData();
            if (skuEntity != null) {
                // 获取每个订单商品的价格小计
                return skuEntity.getPrice().multiply(item.getCount());
            }
            // 不存在这个sku
            return new BigDecimal(0);
            // 求总价  归约
        }).reduce((a, b) -> a.add(b)).get();
        // TODO 限购

        // 3 订单验库存并锁库存 保证原子性 分布式锁 redission
        // 将订单集合转为查询库存的SkuLockVo集合  仅需要商品id和数量count
        List<SkuLockVo> skuLockVos = items.stream().map(item -> {
            SkuLockVo skuLockVo = new SkuLockVo();
            skuLockVo.setSkuId(item.getSkuId());
            skuLockVo.setCount(item.getCount().intValue());
            return skuLockVo;
        }).collect(Collectors.toList());

        ResponseVo<List<SkuLockVo>> lockVoResponseVo = this.wmsClient.checkLock(skuLockVos, orderToken);
        List<SkuLockVo> skuLockVoList = lockVoResponseVo.getData();
        // 如果锁库存成功,集合内应为null 如果不为空,说明锁库存失败
        if (!CollectionUtils.isEmpty(skuLockVoList)){
            // 将锁库存失败的集合转为json字符串给页面
            throw new OrderException(JSON.toJSONString(skuLockVoList));
        }

            UserInfo userInfo = LoginInterceptor.getUserInfo();
            Long userId = userInfo.getUserId();
        try {
            // 4 创建订单
            //远程访问创建订单总表和订单详情表
            this.omsClient.saveOrder(submitVo, userId);
        } catch (Exception e) {
            e.printStackTrace();
            //TODO 分布式事务问题 创建订单失败序号解锁库存 或者创建订单成功但是响应超时了需要将订单信息改为无效订单(修改前状态值需要=0)
        }

        // 5 异步删除购物车中对应的记录  MQ 不影响订单的创建
        Map<String,Object> map = new HashMap<>();
        map.put("userId", userId);
        // 获取送货清单中的所有skuId
        List<Long> skuIds = items.stream().map(OrderItemVo::getSkuId).collect(Collectors.toList());
        map.put("skuIds", JSON.toJSONString(skuIds));
        // 发送异步消息到购物车 userId 和 skuIds
        this.rabbitTemplate.convertAndSend("ORDER_EXCHANGE", "cart.delete", map);
    }
}
